package serial

import (
	"io"
	"sync"
	"time"

	D "websocket_apn/define"
	"websocket_apn/log"

	"github.com/tarm/serial"
)

//STransfer serial_transfer结构体
type STransfer struct {
	port   *serial.Port
	config *serial.Config
	mu     sync.RWMutex

	controllerID   string
	comName        string
	baud           int
	timeout        time.Duration
	byteSize       byte
	parity         serial.Parity
	stopBits       serial.StopBits
	addr           string
	interval       int
	reconnectTimes int
}

//NewSerialTransfer 新建串口通讯器
func NewSerialTransfer(param Param) (*STransfer, error) {
	st := &STransfer{
		controllerID: param.ControllerID,
		comName:      param.ComName,
		baud:         param.Baud,
		timeout:      param.Timeout,
		byteSize:     param.ByteSize,
		parity:       param.Parity,
		stopBits:     param.StopBits,
		addr:         param.Addr,
	}
	log.Infof("NewSerialTransfer Param:%v", param)
	return st, nil
}

//初始化方法
func (st *STransfer) Init() {
	if st.baud == 0 {
		st.baud = 9600
	}
	if st.timeout == 0 {
		st.timeout = 3000
	}
	st.config = &serial.Config{
		Name:        st.comName,
		Baud:        st.baud,
		ReadTimeout: time.Millisecond * st.timeout,
		Size:        st.byteSize,
		Parity:      st.parity,
		StopBits:    st.stopBits,
	}

	_ = st.reconnect()
}

//send 内部方法
func (st *STransfer) send(port *serial.Port, data []byte) (n int, err error) {
	st.mu.RLock()
	defer st.mu.RUnlock()

	err = port.Flush()
	if err != nil {
		return 0, err
	}

	n, err = port.Write(data)
	if err != nil {
		return 0, err
	}

	return n, err
}

//Send 发送
func (st *STransfer) Send(m *D.Message) D.ErrorCode {
	if m == nil || len(m.PayloadBytes) == 0 {
		log.Errorf("ctl: %s, invalid parameter: %v", st.controllerID, m)
		return D.ErrorTransferInvalidParams
	}

	data := m.PayloadBytes
	port, err := st.getPort()
	if err != nil {
		log.Errorf("ctl: %s, com send error: %s", st.controllerID, err)
		return D.ErrorTransferReconnect
	}
	log.Debugf("STransfer begin send data")
	n, err := st.send(port, data)
	if err != nil {
		log.Errorf("ctl: %s, com send error: %s", st.controllerID, err)
	}
	log.Debugf("STransfer send data len:%d", n)
	if n == len(data) {
		return D.Success
	}

	err = st.reconnect()
	if err != nil {
		return D.ErrorTransferReconnect
	}

	return D.ErrorTransferSendError
}

//receive 内部方法
func (st *STransfer) receive(port *serial.Port, receiveLen int) ([]byte, error) {
	var buf = make([]byte, receiveLen)
	var length int

	st.mu.RLock()
	defer st.mu.RUnlock()

	for length = 0; length < receiveLen; {
		if st.interval <= 0 {
			break
		}
		log.Debugf("[%s]STransfer begin read data", st.controllerID)
		n, err := port.Read(buf[length:])
		log.Debugf("[%s]STransfer end read data n=%d  err:%s", st.controllerID, n, err)
		if err != nil {
			log.Errorf("ctl: %s, com receive error: %s", st.controllerID, err)
			if err == io.EOF {
				st.interval--
				continue
			}

			st.interval -= 2
			continue
		}

		length += n
	}

	return buf[:length], nil
}

//Receive 接收
func (st *STransfer) Receive(receiveLen int, msgid int32) (D.ErrorCode, *D.Message) {
	port, err := st.getPort()
	if err != nil {
		return D.ErrorTransferReconnect, nil
	}

	data, err := st.receive(port, receiveLen)
	if err != nil {
		log.Errorf("ctl: %s, com receive error: %s", st.controllerID, err)
	}

	if len(data) != 0 {
		m := &D.Message{PayloadBytes: data}
		return D.Success, m
	}

	err = st.reconnect()
	if err != nil {
		return D.ErrorTransferReconnect, nil
	}

	return D.ErrorTransferSendError, nil
}

//Close 关闭
func (st *STransfer) Close() {
	st.mu.RLock()
	defer st.mu.RUnlock()

	if st.port != nil {
		_ = st.port.Close()
	}
}

//getPort 获取通讯句柄
func (st *STransfer) getPort() (*serial.Port, error) {
	if st.port != nil {
		return st.port, nil
	}

	err := st.reconnect()
	if err != nil {
		return nil, err
	}

	return st.port, nil
}

//重连方法
func (st *STransfer) reconnect() (err error) {
	//st.Close()

	st.mu.Lock()
	defer st.mu.Unlock()

	if st.port != nil {
		_ = st.port.Close()
	}

	for i := 0; i < 3; i++ {
		st.port, err = serial.OpenPort(st.config)
		if err != nil {
			log.Errorf("ctl: %s, reconnect error: %s", st.controllerID, err)
			st.reconnectTimes++
			<-time.After(time.Second)
			continue
		}

		st.reconnectTimes = 0
		return nil
	}

	return err
}
